package com.anonymous.client;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

import com.anonymous.message.CheckPaidUserMsg;
import com.anonymous.message.CheckPaidUserResultMsg;
import com.anonymous.message.LoginMsg;
import com.anonymous.message.LogoutMsg;
import com.anonymous.message.Message;
import com.anonymous.message.PayMsg;
import com.anonymous.message.RegisterMsg;
import com.anonymous.util.ConfidentialitySecurityUtils;
import com.anonymous.util.LoggerUtils;
import com.anonymous.util.NetworkUtils;


public class Client {
	
	private LoggerUtils logger = new LoggerUtils();

	private KeyPair clientKeyPair;

	private SSLSocket clientSSLSocket;

	private NetworkUtils networkUtils;
	private ConfidentialitySecurityUtils confidentialitySecurityUtils;

	/**
	 * KeyStore for storing our public/private key pair
	 */
	private KeyStore clientKeyStore;

	/**
	 * KeyStore for storing the server's public key
	 */
	private KeyStore serverKeyStore;

	/**
	 * Used to generate a SocketFactory
	 */
	private SSLContext sslContext;

	/**
	 * Passphrase for accessing our authentication keystore
	 */
	static private final String passphrase = "clientpw";

	private String serverIP = "";
	private int port = 6677;
	
	

	public KeyPair getClientKeyPair() {
		return clientKeyPair;
	}

	public void setClientKeyPair(KeyPair clientKeyPair) {
		this.clientKeyPair = clientKeyPair;
	}

	public static void main(String[] args) throws Exception {
/*		
		ConfidentialitySecurityUtils confidentialitySecurityUtils = new ConfidentialitySecurityUtils();
		NetworkUtils networkUtils = new NetworkUtils();
		KeyPair clientKeyPair = confidentialitySecurityUtils.generateCustomKeyPair();
		X509Certificate clientCertificate = confidentialitySecurityUtils.generateSelfSignedX509Certificate(clientKeyPair);
		
		KeyStore serverKeyStore = confidentialitySecurityUtils.getServerKeystore();
		Client client = new Client("127.0.0.1",serverKeyStore);
		System.out.println("Connect to Server" + client.getServerIP() + "...");
		if(client.connect())System.out.println("Connected");
		
		BufferedReader inFromUser = new BufferedReader(
				new InputStreamReader(System.in));
		System.out.println("This is Client. Please type:\n");
		String sentence;
		Message result;
		while (true) {
			sentence = inFromUser.readLine();
			ChatMsg chatMsg = new ChatMsg();
			chatMsg.setFromUserName("Client");
			chatMsg.setMessage(sentence);
			networkUtils.send(chatMsg, client.clientSSLSocket);

			result = (Message) networkUtils.receive(client.clientSSLSocket);
			if (!result.getFlag().equals("HELLO"))
				return;
			System.out.println("Recieved");
		}
		
		
		System.out.println("");
		if(client.register("test90", "test90", false, clientCertificate))
			System.out.println("Registered");*/
	}

	// constructor
	public Client(String serverIP, KeyStore serverKeyStore) throws Exception {

		this.serverIP = serverIP;
		networkUtils = new NetworkUtils();
		setupServerKeystore(serverKeyStore);
		setupClientKeyStore();
		setupSSLContext();
	}

	private void setupServerKeystore(KeyStore serverKeyStore) {
		// TODO Auto-generated method stub
		this.serverKeyStore = serverKeyStore;
	}

	// initial connection to server

	public boolean connect() {

		try {
			SSLSocketFactory sf = sslContext.getSocketFactory();
			clientSSLSocket = (SSLSocket) sf.createSocket(serverIP, port);
		} catch (Exception e) {
			logger.LogSevere("Client connect Failed");
			return false;
		}
		logger.LogNormal("Client connected");
		return true;

	}

	private void setupClientKeyStore() throws Exception {
		clientKeyStore = null;
	}

	private void setupSSLContext() throws GeneralSecurityException, IOException {
		TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
		tmf.init(serverKeyStore);

		KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
		kmf.init(clientKeyStore, passphrase.toCharArray());

		sslContext = SSLContext.getInstance("TLS");
		SecureRandom secureRandom = new SecureRandom();
		secureRandom.nextInt();
		sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(),
				secureRandom);
	}

	// logs into server with username and password
	public boolean login(String username, String passwordHash,
			X509Certificate clientCertificate) throws Exception {
		LoginMsg loginMsg = new LoginMsg();
		loginMsg.setUsername(username);
		loginMsg.setPasswordHash(passwordHash);
		loginMsg.setCertificate(clientCertificate);
		networkUtils.send(loginMsg, clientSSLSocket);
		Message result = (Message) networkUtils.receive(clientSSLSocket);
		if (result.getFlag().equals("LOGIN_SUCCESS")) {
			logger.LogNormal("Login Success: "+username);
			return true;
		}
		logger.LogSevere("Login Failed: "+username);
		return false;
	}

	// registers a new username and password with server
	public boolean register(String username, String passwordHash, boolean isPaidUser,
			X509Certificate clientCertificate) throws Exception {
		RegisterMsg registerMsg = new RegisterMsg();
		registerMsg.setUsername(username);
		registerMsg.setPasswordHash(passwordHash);
		registerMsg.setPaidUser(isPaidUser);
		registerMsg.setCertificate(clientCertificate);
		networkUtils.send(registerMsg, clientSSLSocket);
		Message result = (Message) networkUtils.receive(clientSSLSocket);
		if (result.getFlag().equals("REGISTER_SUCCESS")) {
			logger.LogNormal("Register Success: "+username);
			return true;
		}
		logger.LogSevere("Register Failed: "+username);
		return false;
	}

	// sends a chat message to another user at a given ip address
	public void sendChatMessage(PublicKey RcvPublicKey, String ipAddress,
			String message) {
		/*
		 * // Socket clientToClientSocket; try { Client chatClient = new
		 * Client(ipAddress, RcvPublicKey, clientKeyPair); if
		 * (chatClient.connect()) { ChatMsg chatMsg = new ChatMsg();
		 * chatMsg.setFromUserName(userName); chatMsg.setMessage(message);
		 * networkUtils.sendSecure(chatMsg, chatClient.getClientSocket(),
		 * chatClient.getServerSessionKey(), chatClient.getServerMACKey()); }
		 * 
		 * // networkUtils.sendSecure(chatMsg, clientToClientSocket,) } catch
		 * (Exception e) { e.printStackTrace(); }
		 */

	}

	// logs a user out from server
	public void logout() throws Exception {
		try {
			LogoutMsg logoutMsg = new LogoutMsg();
			networkUtils.send(logoutMsg, clientSSLSocket);
			clientSSLSocket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public boolean isPaidUser(String userNameToCjeck){
		CheckPaidUserMsg checkPaidUserMsg = new CheckPaidUserMsg();
		checkPaidUserMsg.setUserNameToCheck(userNameToCjeck);
		Message result = null;
		try {
			networkUtils.send(checkPaidUserMsg, clientSSLSocket);
			result = networkUtils.receive(clientSSLSocket);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if(!result.getFlag().equals("CHECK_PAID_RESULT_MSG"))return false;
		CheckPaidUserResultMsg checkPaidUserResultMsg = (CheckPaidUserResultMsg)(result);
		if(checkPaidUserResultMsg.getUserNameToCheck().equals(checkPaidUserMsg.getUserNameToCheck()) && checkPaidUserResultMsg.isPaidUser()){
			return true;
		}
		else return false;
		
		
	}
	public boolean Pay(){
		PayMsg payMsg = new PayMsg();
		Message result = null;
		try {
			networkUtils.send(payMsg, clientSSLSocket);
			 result = networkUtils.receive(clientSSLSocket);
		} catch (Exception e) {
			e.printStackTrace();
		}

		if(result.getFlag().equals("PAY_SUCCESS")){
			logger.LogSevere("Pay Success");
			return true;
		}
		else if(result.getFlag().equals("PAY_FAIL")){
			logger.LogSevere("Pay Failed");
			return false;
		}
		logger.LogSevere("Pay Failed");
		return false;
	}
	
	
	public String getServerIP() {
		return serverIP;
	}

	public void setServerIP(String serverIP) {
		this.serverIP = serverIP;
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}
	
	
	
}
